.. _Фрагменты и прочие возможности массивов:

Фрагменты и прочие возможности массивов
========================================

.. index::
    фрагмент
    массив

В главе, посвященной массивам (:ref:`Массивы<Массивы>`), мы видели, как элементы группируются в виде
коллекции в массиве. Эта глава была намеренно краткой, оставив большинство функций массивов для этой главы.
Прежде чем идти дальше, вот несколько кратких определений некоторых терминов , которые оказались близкими по значению:

* Массив: Обобщенное понятие группы элементов, расположенных последовательно и доступных по индексам.
* Массив фиксированной длины (статический массив): массив с фиксированным количеством элементов. Этот тип массива владеет своими элементами.
* Динамический массив: массив, вкоторый можно добавлять и из которого можно удалять элементы. Этот тип массива предоставляет доступ к элементам,
  принадлежащим общей среде выполнения.
* Фрагмент: Другое название динамического массива.
  Когда я пишу slice, я буду конкретно иметь в виду slice; а когда я пишу array, я буду
  иметь в виду либо slice, либо массив фиксированной длины, без каких-либо различий.


Фрагменты
----------


Фрагменты - обладают той же функциональностью что и динамические массивы. Они называются динамическими массивами за
то, что используются как массивы, и называются фрагментамии за предоставление доступа к частям
других массивов. Они позволяют использовать эти части, как если бы они были отдельными массивами.
Срезы определяются синтаксисом числового диапазона, который соответствует индексам, указывающим начало и конец диапазона:

.. code-block:: console

    beginning_index .. one_beyond_the_end_index


В данном синтаксисе подразумевается, что начальный индекс есть часть диапазона, но конечный индекс находится за пределами дипазона

.. code-block:: d

    /* ... */ = monthDays[0 .. 3]; // 0, 1, and 2 are included; but not 3

*Примечание: Данные числовые дипазоны отличаются щ=от дипазонов представленых стандартной библиотекой языка D (Phobos). Диапазоны Phobos ranges относятся к
структурам и интерфейсам и интерфейсам классов. Мы рассмотрим данную функциональность позднее*

В качестве примера мы можем представить срезы,ранее определенного массива monthDays, чтобы иметь возможность использовать его части в виде
четырех меньших массивов:

.. code-block:: d

    int[12] monthDays = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
    int[] firstQuarter = monthDays[0 .. 3]
    int[] secondQuarter = monthDays[3 .. 6]
    int[] thirdQuarter = monthDays[6 .. 9]
    int[] fourthQuarter = monthDays[9 .. 12]


Четыре переменные в приведенном выше коде являются фрагментами (срезами); они предоставляют доступ к четырем частям
уже существующего массива. Важный момент, который стоит здесь подчеркнуть, заключается в том, что *у этих
фрагментов нет собственных элементов. Они просто предоставляют доступ к элементам
фактического массива.* Изменение элемента фрагмента так же изменит элемент фактического массива. Чтобы убедиться в этом,
давайте изменим первые элементы каждого среза, а затем напечатаем фактический массив:

.. code-block:: d

    firstQuarter[0] = 1;
    secondQuarter[0] = 2;
    thirdQuarter[0] = 3;
    fourthQuarter[0] = 4;
    writeln(monthDays);

Вывод

.. code-block:: console

    [1, 28, 31, 2, 31, 30, 3, 31, 30, 4, 30, 31]

Каждый фрагмент изменяет свой первый элемент, и это влияет на соответствующий элемент фактического
массива. Ранее мы видели, что допустимые индексы массива располагаются в дипазоне от 0 до величины равной длине массива минус единица.
Например, допустимыми индексами массива из 3 элементов являются 0, 1 и 2. Аналогично, конечный индекс в синтаксисе среза указывает элемент за последним
элементом, к которому срез будет предоставлять доступ. По этой причине, когда последний
элемент массива необходимо включить во фрагмент, длина массива должна быть
указана в качестве конечного индекса. Например, фрагмент всех элементов массива из 3 элементов
был бы массив[0..3]. Очевидным ограничением является то, что начальный индекс не может быть больше
конечного индекса:

.. code-block:: d

    int[3] array = [ 0, 1, 2 ];
    int[] slice = array[2 .. 1]; // ← ошибка времени исполнения

Допустимо при определении индекса, задать равными начальный и конечный индекс, в этом случае определяется пустой срез.
Естественно, предполагаем, что значения индексов валидны.

.. code-block:: d

    int[] slice = anArray[index .. index];
    writeln("The length of the slice: ", slice.length);

Вывод

.. code-block:: console

    The length of the slice: 0

Использование знака $, вместо свойства array.length
----------------------------------------------------

.. index::
    array.length
    длина массива
    $

При индексации знак, $ есть синоним длины массива

.. code-block:: d

    writeln(array[array.length - 1]); // последний элемент массива
    writeln(array[$ - 1]);            // последний элемент массива

Использование свойства .dup для копирования
--------------------------------------------

.. index::
    dup
    copy

.dup есть сокращение от "duplicate", это свойство создаёт новый массив, содержащий копии элементов из существующего

.. code-block:: d

    double[] array = [ 1.25, 3.75 ];
    double[] theCopy = array.dup;

В качестве примера рассмотрим массив содержащий количество дней в каждом месяце в вискосном году. Для этого сделаем копию аналогичного массива
для не вискосного года и увеличим количество дней в феврале.

.. code-block:: d

    import std.stdio;

    void main()
    {
        int[12] monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        int[] leapYear = monthDays.dup;
        ++leapYear[1]; // increments the days in February
        writeln("Non-leap year: ", monthDays);
        writeln("Leap year : ", leapYear);
    }


.. code-block:: console

    Non-leap year: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    Leap year: [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

Присваивание
------------

Мы видели, что оператор присваивания изменяет значение переменной. В случае массива оператор присваивания работатет так же

.. code-block:: d

    int[3] a = [ 1, 1, 1 ];
    int[3] b = [ 2, 2, 2 ];
    a = b; // все элементы стали равными 2
    writeln(a);

Вывод

.. code-block:: console

    [2, 2, 2]

Операция присваивания имеет совершенно другой смысл для фрагментов: она заставляет
срез начать предоставлять доступ к новым элементам:

.. code-block:: d

    int[] odds = [ 1, 3, 5, 7, 9, 11 ];
    int[] evens = [ 2, 4, 6, 8, 10 ];
    int[] slice; // не обеспечивает доступа к элементам массива
    slice = odds[2 .. $ - 2];
    writeln(slice);
    slice = evens[1 .. $ - 1];
    writeln(slice);

В данном примере, фрагмент не предоставляет доступ к элементам массив в момент его определения. Далее он используется для предоставления доступа к элементам массива odds,
затем к элементам массива evens

.. code-block:: console

    [5, 7]
    [4, 6, 8]

Увеличение размера фрагмента может прервать совместное использование
----------------------------------------------------------------------

Так как длина массива фиксированной длины не может быт изменена, то тдея прекращения совместного использования
приложима только к срезам. Возможно определить доступ к одному и тому же элементу из разных срезов.
Например, доступ к первым элементам из восьми предоставляется посредством трех фрагментов


.. code-block:: d

    import std.stdio;

    void main()
    {
        int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
        int[] half = slice[0 .. $ / 2];
        int[] quarter = slice[0 .. $ / 4];
        quarter[1] = 0; // модификация посредствомодного среза
        writeln(quarter);
        writeln(half);
        writeln(slice);
    }

Наблюдаем еффект от модификации в одном срезе - в остальных фрагментах

.. code-block:: console

    [1, 0]
    [1, 0, 5, 7]
    [1, 0, 5, 7, 9, 11, 13, 15]

При таком рассмотрении фрагмент предоставляют общий доступ к элементам. Этот общий доступ
открывает вопрос о том, что происходит, когда в один из срезов добавляется новый элемент.
Поскольку доступ к одним и тем же элементам может быть предоставлен несколькими срезами, может не
хватить места для добавления элементов во фрагмент, не затрагивая элементы других.
D запрещает удаление элемента и отвечает на этот вопрос, разрывая
отношения совместного использования, если для нового элемента нет места: Фрагмент,
которому некуда расти, покидает общий доступ. Когда это происходит, все существующие элементы
фрагменты этого фрагмента автоматически копируются в новое место, и фрагмент начинает предоставлять
доступ к этим новым элементам.
Чтобы увидеть это в действии, давайте добавим элемент в quarter, прежде чем изменять его
второй элемент:

.. code-block:: d

    quarter ~= 42; // this slice leaves the sharing because there is no room for the new element
    quarter[1] = 0; // for that reason this modification does not affect the other slices

Выходные данные программы показывают, что изменение четвертичного среза
не влияет на остальные:

.. code-block:: console

    [1, 0, 42]
    [1, 3, 5, 7]
    [1, 3, 5, 7, 9, 11, 13, 15]

Explicitly increasing the length of a slice makes it leave the sharing as well:
Явно изменим длину среза, что будет причиной потери совместного испоьзования

.. code-block:: d

    ++quarter.length; // потеря совместного использования

или

.. code-block:: d

    quarter.length += 5; // потеря совместного использования

On the other hand, shortening a slice does not affect sharing. Shortening the slice
merely means that the slice now provides access to fewer elements:

С другой стороны уменьшение длины среза не влияет на совместное использование
Уменьшение длины срезы означает что срез предоставляет доступ к меньшему числу элементов

.. code-block:: d

    int[] a = [ 1, 11, 111 ];
    int[] d = a;
    d = d[1 .. $]; // уменьшение длины, начинаем с элемента 1
    d[0] = 42;     // modifying the element through the slice
    writeln(a); // printing the other slice

Как видно из выходных данных, модификация через d видна через a;
совместное использование по-прежнему действует:

.. code-block:: console

    [1, 42, 111]

уменьшение длины другим способом так же не влияет на совместное использование

.. code-block:: d

    d    = d[0 .. $ - 1];     // уменьшаем длину с конца
    --d.length;               // то же самое
    d.length = d.length - 1;  // то же самое


Совместное использование всё равно работает

**Использование свойство capacity для определения будет ли совместное исполтьзование прекращено**

.. index::
    capacity

Бывают случаи, когда фрагменты продолжают совместно использовать элементы даже после
добавления элемента в один из них. Это происходит, когда элемент добавляется в самый длинный фрагмент
и в конце его остается место:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
        int[] half = slice[0 .. $ / 2];
        int[] quarter = slice[0 .. $ / 4];
        slice ~= 42; // добавляем к самому длинному
        slice[1] = 0; // модифицируем элемент
        writeln(quarter);
        writeln(half);
        writeln(slice);
    }



Как видно из выходных данных, хотя добавленный элемент увеличивает длину фрагмента,
совместное использование не было прекращено, и модификация видна во всех
фрагментах:

.. code-block:: console

    [1, 0]
    [1, 0, 5, 7]
    [1, 0, 5, 7, 9, 11, 13, 15, 42]

Свойство capacity фрагментов определяет, будет ли
прекращен общий доступ, если элемент добавлен к определенному фрагменту. (емкость на самом деле является
функцией, но это различие не имеет никакого значения в данном обсуждении.)

Значение свойства capacity имеет следующие значения:

* Когда его значение равно 0, это означает, что это не самый длинный исходный фрагмент. В этом
  случае добавление нового элемента определенно переместило бы элементы фрагмента
  и совместное использование прекратилось бы.
* Когда его значение не равно нулю, это означает, что это самый длинный исходный фрагмент. В
  данном случае емкость обозначает общее количество элементов, которые этот фрагмент может
  содержать без необходимости копирования. Количество новых элементов, которые могут быть
  добавлены, может быть вычислено путем вычитания фактической длины среза из
  значение емкости. Если длина фрагмента равна его емкости, то фрагмент будет
  скопирован в новое местоположение при добавлении еще одного элемента.
  Соответственно, программа, которой необходимо определить,
  завершится ли общий доступ, должна использовать логику, аналогичную следующей:

.. code-block:: d

    if (slice.capacity == 0)
    {
        /* Its elements would be relocated if one more element * is added to this slice. */
        // ...
    }
    else
    {
        /* этот срез еще имеет место для неовых элементов прежде придётся проводить новое распределение памяти
        * посмотри сколько ещё есть места */
        auto howManyNewElements = slice.capacity - slice.length;
    }


Интересный случай - это когда для всех элементов имеется более одного среза. В
таком случае все срезы сообщают о наличии емкости:

.. code-block:: d

    import std.stdio;

    void main()
    {
        // Three slices to all elements
        int[] s0 = [1, 2, 3, 4];
        int[] s1 = s0;
        int[] s2 = s0;
        writeln(s0.capacity);
        writeln(s1.capacity);
        writeln(s2.capacity);
    }


Все срезы имеют свободное пространство

.. code-block:: console

    7
    7
    7

Однако, как только элемент добавляется в один из срезов, емкость
остальных падает до 0:

.. code-block:: d

    s1 ~= 42; //  теперь самый длинный
    writeln(s0.capacity);
    writeln(s1.capacity);
    writeln(s2.capacity);

Поскольку фрагмент с добавленным элементом теперь самый длинный, он единственный, у
которого есть емкость:

.. code-block:: console

    0
    7  только здесь есть доступное пространство
    0

**Резервирование пространства для элементов**

Копирование элементов и выделение новой памяти для увеличения емкости сопряжены
с определенными затратами. По этой причине добавление элемента может быть дорогостоящей операцией.
Когда количество добавляемых элементов известно заранее, можно зарезервировать емкость для элементов:


.. code-block:: d

    import std.stdio;

    void main()
    {
        int[] slice;
        slice.reserve(20);
        writeln(slice.capacity);
        foreach (element; 0 .. 17)
        {
            slice ~= element; // места досточно перемещения элементов не будет
        }
    }


.. code-block:: console

     31  Резервирование для 20 элементов

Элементы slice будут перемещены только после того, как их будет более 31 (,???)
элемента.

Операции для всех элементов
----------------------------

Эта функциональность определена как для динамических массивов так и для массивов фиксированной длины
Символы [], записанные после имени массива, означают все элементы. Эта
функция упрощает работу программы, когда необходимо применить определенные операции ко всем
элементам массива.

.. code-block:: d

    import std.stdio;

    void main()
    {
        double[3] a = [10, 20, 30];
        double[3] b = [2, 3, 4];
        double[3] result = a[] + b[];
        writeln(result);
    }


Вывод

.. code-block:: console

    [12, 23, 34]


Операция сложения в этой программе применяется к соответствующим элементам
обоих массивов по порядку: сначала складываются первые элементы, затем вторые
элементы и т.д. Естественным требованием является то, что длины двух массивов должны быть равны.
Оператором может быть один из арифметических операторов + , - , * , / , % , и ^^ ; один из
двоичных операторов ^ , & и | ; а также унарные операторы - и ~, которые
вводятся перед массивом. Мы познакомимся с некоторыми из этих операторов в последующих главах.
Также можно использовать варианты присваивания этих
операторов: = , += , -= , \*= , /= , %= , ^^= , ^= \&= , и \|=
Эта функция работает не только с двумя массивами; ее также можно использовать с массивом
и совместимым выражением. Например, следующая операция делит все
элементы массива на четыре:

.. code-block:: console

    double[3] a = [ 10, 20, 30 ];
    a[] /= 4;
    writeln(a);

.. code-block:: console

    [2.5, 5, 7.5]

Для присвоения одного значения всем элементам можно сделать так

.. code-block:: d

    a[] = 42;
    writeln(a);

Вывод

.. code-block:: console

    [42, 42, 42]

Эта функция требует большого внимания при использовании со срезами. Хотя
очевидной разницы в значениях элементов нет, следующие два выражения имеют очень
разные значения:

.. code-block:: d

    slice2 = slice1; //  ткрываем общийдоступ кэлементам slice2 и slice1
    slice3[] = slice1; //  изменяем все значения slice3

Назначение slice2 приводит к тому, что он использует те же элементы, что и slice1 . С
другой стороны, поскольку slice3[] означает все элементы slice3 ,
элементы становятся такими же, как значения элементов slice1. Эффект
присутствия или отсутствия символов [] нельзя игнорировать.
Мы можем увидеть пример этого различия в следующей программе:

.. code-block:: d

    import std.stdio;

    void main()
    {
        double[] slice1 = [1, 1, 1];
        double[] slice2 = [2, 2, 2];
        double[] slice3 = [3, 3, 3];
        slice2 = slice1; // ← slice2 starts providing access
        //to the same elements that
        //slice1 provides access to
        slice3[] = slice1; // ← the values of the elements of
        //slice3 change
        writeln("slice1 before: ", slice1);
        writeln("slice2 before: ", slice2);
        writeln("slice3 before: ", slice3);
        slice2[0] = 42; // ← the value of an element that
        //it shares with slice1 changes
        slice3[0] = 43; // ← the value of an element that
        //only it provides access to
        //changes
        writeln("slice1 after : ", slice1);
        writeln("slice2 after : ", slice2);
        writeln("slice3 after : ", slice3);
    }


Модификация slice 2 также влияет на slice1:

.. code-block:: console

        slice1 before: [1, 1, 1]
        slice2 before: [1, 1, 1]
        slice3 before: [1, 1, 1]
        slice1 after:  [42, 1, 1]
        slice2 after:  [42, 1, 1]
        slice3 after:  [42, 1, 1]

Опасность здесь заключается в том, что потенциальная ошибка может быть замечена только после изменения значения
общего элемента.

Многомерные массивы
-------------------

.. index::
    Многомерные массивы

До сих пор мы использовали массивы только с основными типами, такими как int и double . Тип
элемента на самом деле может быть любым другим типом, включая другие массивы. Это позволяет
программисту определять сложные контейнеры, такие как array of arrays. Массивы
массивов называются многомерными массивами. Элементы всех массивов, которые мы определили до сих пор, были записаны
в исходном коде слева направо. Чтобы помочь нам понять концепцию двумерного
массива, давайте на этот раз определим массив сверху вниз:

.. code-block:: d

    int[] array = [
                    10,
                    20,
                    30,
                    40
                  ]

Как вы помните, большинство пробелов в исходном коде используются для удобства
чтения и не меняют смысла кода. Приведенный выше массив мог
бы быть определен в одной строке и имел бы то же значение.

Давайте теперь заменим каждый элемент этого массива другим массивом:

.. code-block:: d

    int[] array = [
                    [10, 11, 22],
                    [20, 21, 22],
                    [30, 31, 32],
                    [40, 41, 42]
                  ]

Мы заменили элементы типа int элементами типа int[]. Чтобы привести
код в соответствие с синтаксисом определения массива, теперь мы должны указать тип
элементов как int[] вместо int :

.. code-block:: d

    int[][] array = [
                    [10, 11, 22],
                    [20, 21, 22],
                    [30, 31, 32],
                    [40, 41, 42]
                  ]

Такие массивы называются двумерными массивами, потому что их можно рассматривать как имеющие
строки и столбцы. Двумерные массивы используются так же, как и любой другой массив, если
мы помним, что каждый элемент сам по себе является массивом и используется в операциях с массивами:

.. code-block:: d

    array ~= [ 50, 51 ]; // добавляем элемент
    array[0] ~= 13;      // добавляем элемент к первой строке

состояние массива теперь

.. code-block:: console

    [[10, 11, 12, 13], [20, 21, 22], [30, 31, 32], [40, 41, 42], [50, 51]]

Массивы и элементы также могут иметь фиксированную длину. Ниже
приведен трехмерный массив, в котором все измерения имеют фиксированную длину:

.. code-block:: d

    int[2][3][4] array; // 2 колонки, 3 строки, 4 страницы

Приведенное выше определение можно рассматривать как четыре страницы, состоящие из трех строк по два столбца
целых чисел. В качестве примера, такой массив можно использовать для представления 4-этажного здания
в приключенческой игре, каждый этаж состоит из 2x3 = 6 комнат.
Например, количество предметов в первой комнате второго этажа может быть
увеличено следующим образом:

.. code-block:: d

    // Индекс второго этажа =  1, тогда первая комната на этом этаже
    // может быть найдена через индексы [0][0]
    ++itemCounts[1][0][0];

В дополнение к приведенному выше синтаксису, новое выражение также можно использовать для создания
фрагмента фрагментов. В следующем примере используются только два измерения:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int[][] s = new int[][](2, 3);
        writeln(s);
    }


Новое выражение, приведенное выше, создает 2 среза, содержащих по 3 элемента в каждом, и возвращает
срез, который предоставляет доступ к этим срезам и элементам. Выходные данные:

.. code-block:: console

    [[0, 0, 0], [0, 0, 0]]

Итоги
------


Массивы фиксированной длины владеют своими элементами; срезы предоставляют доступ к элементам, которые
не принадлежат исключительно им.

* В операторе [] $ является эквивалентом array_name.длина.
* .dup создает новый массив, состоящий из копий элементов существующего массива.
* В случае массивов фиксированной длины операция присваивания изменяет значения
  элементов; в случае срезов это приводит к тому, что срезы начинают предоставлять доступ к другим
  элементам.
* Фрагменты, которые становятся длиннее, могут прекратить совместное использование элементов и начать предоставлять доступ к
  вновь скопированным элементам. емкость определяет, будет ли это так.
* Синтаксический массив[] означает все элементы массива; операция, которая
  применяется к нему, применяется к каждому элементу в отдельности.
* Массивы массивов называются многомерными массивами.


Упражнения
-----------

Выполните итерацию по элементам массива double и сократите вдвое те, которые
больше 10. Например, учитывая следующий массив:

.. code-block:: d

    double[] array = [ 1, 20, 2, 30, 7, 11 ];

требуемый ответ

.. code-block:: console

    [1, 10, 2, 15, 7, 5.5]

Хотя существует множество решений этой проблемы, попробуйте использовать только функции
срезов. Вы можете начать со среза, который предоставляет доступ ко всем элементам. Затем вы можете
сократить среза с самого начала и всегда использовать первый элемент.
Следующее выражение сокращает фрагмент с самого начала:

.. code-block:: d

    slice = slice[1 .. $];

Решение на странице (???)

